R""( do

-- This Lua code provides some built-in utilities for writing Lua add-ons
-- It is eval'd automatically once BLLua3 has loaded the TS API and environment
-- It only has access to the sandboxed lua environment, just like user code.

-- Implement print using ts.echo so it prints to BL console
print = function(...)
	local t = {...}; local o = {};
	for i = 1, #t do o[i] = tostring(t[i]) end
	ts.echo(table.concat(o, "    "))
end

-- Provide limited OS functions
os = {
	time = function() return math.floor(tonumber(ts.call("getSimTime"))/1000) end,
	clock = function() return tonumber(ts.call("getSimTime"))/1000 end,
}

-- schedule: Use TS's schedule function to schedule lua calls
ts._schedule_table  = ts._schedule_table or {}
ts._schedule_by_obj = ts._schedule_by_obj or {}
ts._schedule_nextid = ts._schedule_nextid or 1
function schedule(time, func, ...)
	-- get a unique schedule ID
	local sched_id = ts._schedule_nextid
	ts._schedule_nextid = ts._schedule_nextid+1
	-- create a callback to call the function when the schedule ends
	local fargs = {...}
	local callback = function()
		func(unpack(fargs))
	end
	-- put it in the schedule table
	ts._schedule_table[sched_id] = { [1] = callback }
	-- use ts schedule to schedule the event
	local sched_obj = tonumber(ts.call("schedule", time, 0, "luacall", "_ts_schedule_callback", sched_id))
	-- save the schedule reference in a table and return the id
	ts._schedule_table[sched_id][2] = sched_obj
	return sched_id
end
function _ts_schedule_callback(sched_id)
	-- get schedule out of schedule table by id
	sched_id = tonumber(sched_id)
	local sched = ts._schedule_table[sched_id]
	assert(sched, "_ts_schedule_callback: no schedule with id "..sched_id)
	ts._schedule_table[sched_id] = nil
	-- call it
	sched[1]()
end
function cancel(sched_id_s)
	sched_id = tonumber(sched_id_s)
	-- get the schedule by id out of the table
	local sched = ts._schedule_table[sched_id]
	if not sched then return end
	-- cancel it in ts
	ts.call("cancel", sched[2])
	-- remove from table
	ts._schedule_table[sched_id] = nil
end

-- wrap io.open to allow reading from zips
-- Not perfect because TS file I/O sucks - Can't read nulls, can't distinguish between CRLF and LF.
local file_meta = {
	read = function(file, mode)
		file:_init()
		if not file or type(file)~="table" or not file._is_file then error("File:read: Not a file", 2) end
		if file._is_open ~= true then error("File:read: File is closed", 2) end
		if mode=="*n" then
			local ws, n = file.data:match("^([ \t\r\n]*)([0-9%.%-e]+)", file.pos)
			if n then
				file.pos = file.pos + #ws + #n
				return n
			else
				return nil
			end
		elseif mode=="*a" then
			local d = file.data:sub(file.pos, #file.data)
			file.pos = #file.data + 1
			return d
		elseif mode=="*l" then
			local l, ws = file.data:match("^([^\r\n]*)(\r?\n)", file.pos)
			if not l then
				l = file.data:match("^([^\r\n]*)$", file.pos); ws = "";
				if l=="" then return nil end
			end
			if l then
				file.pos = file.pos + #l + #ws
				return l
			else
				return nil
			end
		elseif type(mode)=="number" then
			local d = file.data:sub(file.pos, file.pos+mode)
			file.pos = file.pos + #d
			return d
		else
			error("File:read: Invalid mode \""..mode.."\"", 2)
		end
	end,
	lines = function(file)
		file:_init()
		return function()
			return file:read("*l")
		end
	end,
	close = function(file)
		if not file._is_open then error("File:close: File is not open", 2) end
		file._is_open = false
	end,
	__index = function(f, k) return rawget(f, k) or getmetatable(f)[k] end,
	_init = function(f)
		if not f.data then
			f.data = ts.call("_bllua3_ReadEntireFile", f.filename)
		end
	end,
}
local function new_file_obj(fn)
	local file = {
		_is_file = true,
		_is_open = true,
		pos = 1,
		__index = file_meta.__index,
		filename = fn,
		data = nil,
	}
	setmetatable(file, file_meta)
	return file
end
local tflip = function(t) local u = {}; for _, n in ipairs(t) do u[n] = true end; return u; end
local bl_allowed_zip_dirs = tflip{"add-ons", "base", "config", "saves", "screenshots"}
local function io_open_absolute(fn, mode)
	-- use original mode if possible
	local res, err = _bllua_io_open(fn, mode)
	if res then return res end
	
	-- otherwise, if TS sees file but Lua doesn't, it must be in a zip, so use TS reader
	local dir = fn:match("^[^/]+")
	if not bl_allowed_zip_dirs[dir:lower()] then return nil, "File is not in one of the allowed directories" end
	local exist = ts.call("isFile", fn) == "1"
	if not exist then return nil, err end
	
	if mode~=nil and mode~="r" then return nil, "Files in zips can only be opened in read mode" end
	
	-- return a temp lua file object with the data
	local fi = new_file_obj(fn)
	return fi
end

io = {
	open = function(fn, mode, errn)
		errn = errn or 1
		
		-- try to open the file with relative path, otherwise use absolute path
		local curfn = debug.getfilename(errn + 1) or ts.get("Con::File")
		if curfn == "" then curfn = nil end
		if fn:find("^%.") then
			local relfn = curfn and fn:find("^%./") and curfn:gsub("[^/]+$", "")..fn:gsub("^%./", "")
			if relfn then
				local fi, err = io_open_absolute(relfn, mode, errn+1)
				return fi, err, relfn
			else
				return nil, "Invalid path", fn
			end
		else
			local fi, err = io_open_absolute(fn, mode, errn+1)
			return fi, err, fn
		end
	end,
	lines = function(fn)
		local fi, err, fn2 = io.open(fn, nil, 2)
		if not fi then error("Error opening file \""..fn2.."\": "..err, 2) end
		return fi:lines()
	end,
	type = function(f)
		if type(f)=="table" and f._is_file then
			return f._is_open and "file" or "closed file"
		else
			return _bllua_io_type(f)
		end
	end,
}

-- provide dofile and require
function dofile(fn, errn)
	errn = errn or 1
	
	local fi, err, fn2 = io.open(fn, "r", errn+1)
	if not fi then error("Error executing file \""..fn2.."\": "..err, errn+1) end
	
	print("Executing "..fn2)
	local text = fi:read("*a")
	fi:close()
	return eval("--[["..fn2.."]]"..text)
end

-- provide eval
function eval(code)
	return assert(loadstring(code))()
end

local function file_exists(fn, errn)
	local fi, err, fn2 = io.open(fn, "r", errn+1)
	if fi then
		return fn2
	else
		return nil
	end
end
function require(fn)
	local fn1 = "./"..(fn:gsub("%.", "/"))..".lua"
	local fn2 = "luainc/"..(fn:gsub("%.", "/"))..".lua"
	
	local fn3 = file_exists(fn1, 2) or file_exists(fn2, 2)
	if fn3 then
		return dofile(fn3, 2)
	else
		return requiresecure(fn)
	end
end

-- get and set globals
function ts.get(name) return ts.call("_bllua_get_var", name) end
function ts.set(name, val) ts.call("_bllua_set_var", name, val) end
function ts.getobj(obj, name) return ts.call("_bllua_get_var_obj", obj, name) end
function ts.setobj(obj, name, val) ts.call("_bllua_set_var_obj", obj, name, val) end

function _bllua_get_var(name) return _G[name] end
function _bllua_set_var(name, val) _G[name] = val end

-- ts.eval and ts.exec
function ts.eval(code) return ts.call("eval", code) end
function ts.exec(file) return ts.call("exec", file) end

-- packages
--ts._package_table = ts._package_table or {}
--function _bllua_call_package(id, ...)
--	id = tonumber(id)
--	local func = ts._package_table[id] or error("no function with id "..id)
--	--local parent = 
--	return func(parent, ...)
--end
--function ts.package(pname, fname, func)
--	local arglist = [[%a, %b, %c, %d, %e, %f, %g, %h, %i, %j, %k, %l, %m, %n, %o, %p, %q, %r, %s, %t]]
--	ts.eval [[
--		package ]]..pname..[[ {
--			function ]]..fname..[[ (]]..arglist..[[) {
--				return luacall("_bllua_call_package", ]]..func_id_pre..[[, ]]..arglist..[[);
--			}
--		};
--	]]
--end

function ts.toBoolean(v) local n = tonumber(v); return n ~= nil and n ~= 0; end
function ts.toNumber(v) return tonumber(v) end

-- Utility for getting the current filename
function debug.getfilename(level)
	if type(level) == "number" then level = level+1 end
	local info = debug.getinfo(level)
	if not info then return nil end
	local filename = info.source:match("^%-%-%[%[([^%]]+)%]%]")
	return filename
end

-- Called when pcall fails on a ts->lua call, used to print detailed error info
function _bllua_on_error(err)
	err = err:match(": (.+)$") or err
	local tracelines = {err}
	local level = 2
	while true do
		local info = debug.getinfo(level)
		if not info then break end
		local filename = debug.getfilename(level) or info.short_src
		local funcname = info.name
		if funcname=="dofile" then break end
		table.insert(tracelines, string.format("%s:%s in function \'%s\'",
			filename,
			info.currentline==-1 and "" or info.currentline..":",
			funcname
		))
		level = level+1
	end
	return table.concat(tracelines, "\n")
end



print("    Lua util loaded")

end )""